﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Schedule.Support
{
	/// <summary>
	/// Předpis pro třídu, která chce být informovaná o průběhu a dokončení operací z výkonných metod (typicky na pozadí)
	/// </summary>
	public interface ICallBackTarget
	{
		/// <summary>
		/// Metoda, která je volána při jakémkoli požadavku z výkonné vrstvy do volající vrstvy.
		/// Druh požadavku a data jsou specifikována v argumentu.
		/// Jde o změnu progresu, dělení progresu na segmenty, oznámení o dokončení běhu funkce.
		/// </summary>
		/// <param name="sender">Odesílatel události</param>
		/// <param name="args">Data o průběhu/stavu</param>
		void CallTarget(object sender, CallBackTargetArgs args);
	}
	/// <summary>
	/// Data předávaná třídě, která je cílem volání (ICallBackTarget) z výkonných metod (na pozadí)
	/// </summary>
	public class CallBackTargetArgs : EventArgs
	{
		#region KONSTRUKTORY
		public CallBackTargetArgs()
		{ }
		public static CallBackTargetArgs CreateTitle(string formTitle, string subTitle)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.SetTitle;
			args.FormTitle = formTitle;
			args.SubTitle = subTitle;
			return args;
		}
		public static CallBackTargetArgs CreateProgress(decimal ratio)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.DoProgress;
			args.Ratio = ratio;
			return args;
		}
		public static CallBackTargetArgs CreateProgress(int current, int count)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.DoProgress;
			args._SetRatio(current, count);

			return args;
		}
		public static CallBackTargetArgs CreateProgress(string textLines)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.ShowText;
			args.TextLines = textLines;
			return args;
		}
		public static CallBackTargetArgs CreateProgress(decimal ratio, string textLines)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.DoProgress;
			args.Ratio = ratio;
			args.TextLines = textLines;
			return args;
		}
		public static CallBackTargetArgs CreateProgress(int current, int count, string textLines)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.DoProgress;
			args._SetRatio(current, count);
			args.TextLines = textLines;
			return args;
		}
		public static CallBackTargetArgs CreateSegment(decimal fromRatio, decimal toRatio)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.CreateSegment;
			args.FromRatio = fromRatio;
			args.ToRatio = toRatio;
			return args;
		}
		internal static CallBackTargetArgs CreateSucceed()
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.Succeed;
			return args;
		}
		internal static CallBackTargetArgs CreateFail(Exception exc)
		{
			CallBackTargetArgs args = new CallBackTargetArgs();
			args.ProcessState = CallBackProcessState.Failed;
			args.Exc = exc;
			return args;
		}
		#endregion
		#region PROPERTIES
		public CallBackProcessState ProcessState { get; set; }
		public decimal Ratio { get; set; }
		public decimal FromRatio { get; set; }
		public decimal ToRatio { get; set; }
		public string FormTitle { get; set; }
		public string SubTitle { get; set; }
		public string TextLines { get; set; }
		public ProgressSegment ProgrSegment { get; set; }
		public Exception Exc { get; set; }
		#endregion
		#region PRIVATE
		private void _SetRatio(int current, int count)
		{
			if (count != 0)
				this.Ratio = ((decimal)current) / ((decimal)count);
		}
		#endregion
	}
	/// <summary>
	/// Stavy výkonného procesu
	/// </summary>
	public enum CallBackProcessState
	{
		Initialized,
		SetTitle,
		Running,
		DoProgress,
		CreateSegment,
		ShowText,
		Failed,
		Succeed
	}
	#region CLASS ProgressCalc + ProgressSegment : TŘÍDY PRO ŘÍZENÍ SEGMENTOVANÉHO PROGRESU (KALKULACE, SEGMENTY)
	/// <summary>
	/// Řídící jednotka pro progress bar - se schopnostmi dělení prostoru na sub-segmenty
	/// </summary>
	public class ProgressCalc
	{
		#region KONSTRUKCE
		public ProgressCalc(Action<decimal> setPositionMethod)
		{
			this._SetPositionMethod = setPositionMethod;
			this._SegmentList = new List<ProgressSegment>();
			this.SegmentOpen(0M, 1M);
		}
		/// <summary>
		/// Metoda, která je volána vždy, když je třeba nastavit hodnotu progresu.
		/// </summary>
		private Action<decimal> _SetPositionMethod;
		/// <summary>
		/// Soupis aktuálně vnořených segmentů.
		/// Na pozici [0] je vždy segment pro rozmezí 0 až 1.
		/// Více viz metoda this._SegmentCalc()
		/// </summary>
		private List<ProgressSegment> _SegmentList;
		#endregion
		#region DIVIDED SEGMENTS
		/// <summary>
		/// Otevře nový segment pro daný rozsah, v rámci aktuálního segmentu.
		/// Postupné vnořování segmentů není omezené.
		/// </summary>
		/// <param name="fromRatio"></param>
		/// <param name="toRatio"></param>
		/// <returns></returns>
		public ProgressSegment SegmentOpen(decimal fromRatio, decimal toRatio)
		{
			ProgressSegment segment = new ProgressSegment(this, fromRatio, toRatio);
			this._SegmentList.Add(segment);
			this._SetProgress(0M);                 // Nastavím ukazatel na počátek segmentu
			return segment;
		}
		/// <summary>
		/// Zavře daný segment = ukončí jeho platnost, tím otevře segment nadřazený.
		/// </summary>
		/// <param name="segment"></param>
		internal void SegmentClose(ProgressSegment segment)
		{
			if (segment == null) return;
			int sgmIdx = this._SegmentList.FindIndex(s => Object.ReferenceEquals(segment, s));   // Najdu index předaného segmentu
			if (sgmIdx < 0) return;

			// Pozor: když bych našel segment == 0, tak ten neodeberu (ten si nechávám vždy), ale odeberu všechny za ním:
			if (sgmIdx == 0)
				sgmIdx = 1;

			this._SetProgress(1M);                 // Nastavím ukazatel na konec segmentu

			// Odeberu daný segment a všechny segmenty za ním následující:
			while (sgmIdx < this._SegmentList.Count)
				this._SegmentList.RemoveAt(sgmIdx);
		}
		#endregion
		#region PROVEDENÍ PROGRESU
		/// <summary>
		/// Zajistí zobrazení progresu dle daných dat.
		/// Řeší stavy: CallBackProcessState.DoProgress : vyvolá DoProgress(args.Ratio);
		/// CallBackProcessState.CreateSegment : zajistí otevření segmentu pro rozmezí args.FromRatio, args.ToRatio a uložení segmentu do args.ProgrSegment
		/// </summary>
		/// <param name="args"></param>
		public void CallProgress(CallBackTargetArgs args)
		{
			switch (args.ProcessState)
			{
				case CallBackProcessState.DoProgress:
					this.DoProgress(args.Ratio);
					break;
				case CallBackProcessState.CreateSegment:
					args.ProgrSegment = this.SegmentOpen(args.FromRatio, args.ToRatio);
					break;
			}
		}
		/// <summary>
		/// Nastaví pozici progresu na danou hodnotu.
		/// Hodnota je v rozmezí 0 až 1, které se zobrazuje v aktuálním segmentu.
		/// </summary>
		/// <param name="ratio"></param>
		public void DoProgress(decimal ratio)
		{
			this._SetProgress(ratio);
		}
		/// <summary>
		/// Zajistí omezení vstupní hodnoty do mezí 0 až 1.
		/// </summary>
		/// <param name="ratio"></param>
		/// <returns></returns>
		internal static decimal BoundRatio(decimal ratio)
		{
			if (ratio < 0M) return 0M;
			if (ratio > 1M) return 1M;
			return ratio;
		}
		/// <summary>
		/// Nastaví danou hodnotu progresu v rámci aktuálního segmentu.
		/// </summary>
		/// <param name="ratio"></param>
		private void _SetProgress(decimal ratio)
		{
			this.CurrentSegmentRatio = BoundRatio(ratio);

			// Určím reálný rozsah aktuálního segmentu:
			this._SegmentCalc();
			decimal position = this.CurrentSegmentBegin + (this.CurrentSegmentRatio * this.CurrentSegmentLength);

			this._SetPosition(position);
		}
		/// <summary>
		/// Určí fyzický rozsah pozici aktuálního segmentu v rozmezí 0 až 1 (což odpovídá celému progress baru).
		/// Vyhodnotí celou linii vnoření segmentů od segmentu [0] (což je celý progress 0 až 1) přes další segmenty, 
		/// kde každý další segment bude mít svůj vlastní rozsah, který určuje jeho relativní pozici v prostoru předešlého (nadřízeného) segmentu.
		/// </summary>
		/// <remarks>
		/// Segment [0] má rozsah (0 - 1)       (B0-E0),
		/// segment [1] má rozsah (0.30 - 0.80) (B1-E1),
		/// segment [2] má rozsah (0.20 - 0.50) (B2-E2)
		///    obsazuje tedy daný prostor z prostoru svého nadřízeného, tedy: B2r = (B1=0.30 + (B2=0.20 * (E1=0.80 - B1=0.30))) až (
		/// </remarks>
		/// <param name="begin"></param>
		/// <param name="length"></param>
		private void _SegmentCalc()
		{
			decimal begin = 0M;
			decimal length = 1M;
			foreach (ProgressSegment sgm in this._SegmentList)
			{
				begin = begin + (sgm.FromRatio * length);   // reálný začátek tohoto segmentu = stávající začátek + zdejší začátek v poměru k stávající (parent) délce
				length = length * sgm.Length;               // reálná délka aktuálního segmentu = poměrná délka segmentu v rámci stávající (parent) délky
			}
			this.CurrentSegmentBegin = begin;
			this.CurrentSegmentLength = length;
		}
		/// <summary>
		/// Nastaví pozici ukazatele na danou hodnotu position, jejíž rozmezí 0 až 1 převede na hodnotu 0 = this.Minimum, 1 = this.Maximum.
		/// Zde jde již o fyzické ovládání progres baru.
		/// </summary>
		/// <param name="position"></param>
		private void _SetPosition(decimal position)
		{
			position = BoundRatio(position);
			this.CurrentPosition = position;

			if (this._SetPositionMethod != null)
				this._SetPositionMethod(position);
		}
		/// <summary>
		/// Aktuální pozice progresu úplná, v rozsahu 0 - 1, odpovídá celému progresu
		/// </summary>
		public decimal CurrentPosition { get; private set; }
		/// <summary>
		/// Aktuální pozice progresu v rámci aktuálního segmentu
		/// </summary>
		public decimal CurrentSegmentRatio { get; private set; }
		/// <summary>
		/// Aktuální segment, jeho počátek v plné souřadnici 0 - 1, odpovídá celému progresu
		/// </summary>
		public decimal CurrentSegmentBegin { get; private set; }
		/// <summary>
		/// Aktuální segment, jeho délka v plné souřadnici 0 - 1, odpovídá celému progresu
		/// </summary>
		public decimal CurrentSegmentLength { get; private set; }
		#endregion
	}
	/// <summary>
	/// Paměť segmentu progresu
	/// </summary>
	public class ProgressSegment : IDisposable
	{
		#region KONSTRUKCE A PROPERTY
		internal ProgressSegment(ProgressCalc parent, decimal fromRatio, decimal toRatio)
		{
			this.Parent = parent;
			this.FromRatio = ProgressCalc.BoundRatio(fromRatio);
			this.ToRatio = ProgressCalc.BoundRatio(toRatio);
			if (this.ToRatio < this.FromRatio)
				this.ToRatio = this.FromRatio;
		}
		public override string ToString()
		{
			return "Segment from: " + this.FromRatio.ToString() + " to: " + this.ToRatio.ToString();
		}
		/// <summary>
		/// Parent objekt progresu, ten je volán v Dispose (jeho metoda SegmentClose()).
		/// </summary>
		internal ProgressCalc Parent { get; private set; }
		/// <summary>
		/// Počátek tohoto segmentu, v prostoru vyššího segmentu
		/// </summary>
		internal decimal FromRatio { get; private set; }
		/// <summary>
		/// Konec tohoto segmentu, v prostoru vyššího segmentu
		/// </summary>
		internal decimal ToRatio { get; private set; }
		/// <summary>
		/// Délka tohoto segmentu, v prostoru vyššího segmentu
		/// </summary>
		internal decimal Length { get { return (this.ToRatio - this.FromRatio); } }
		#endregion
		#region IDisposable Members
		void IDisposable.Dispose()
		{
			if (this.Parent != null)
				this.Parent.SegmentClose(this);
			this.Parent = null;
		}
		#endregion
	}
	#endregion
}
